<?php

/**
 *
 * 简单封装PDO操作
 * Class DB_PDO
 *
 */
class DB_PDO{
    /**
     * 保存类实例的静态成员变量
     * @var
     */
    private static $_instance;
    /**
     * PDO对象
     * @var null
     */
    protected static $dbh = null;
    /**
     * 数据表名
     * @var null
     */
    public  $table_name = null;
    /**
     * where 查询条件
     * @var null
     */
    protected $_where = null;
    /**
     * 拼装的sql语句
     * @var null
     */
    protected $_sql = null;
    /**
     *预处理绑定的阐述
     * @var array
     */
    protected $_bind = array();
    /**
     * 排序条件
     * @var null
     */
    protected $_order = null;
    /**
     * 分页条件
     * @var null
     */
    protected $_limit = null;
    /**
     * 主键 //暂时没用到
     * @var string
     */
    protected $primaryKey = 'id';
    /**
     * 分页默认页大小
     * @var int
     */
    protected $perPage = 15;
    /**
     * 是否开启事务
     * @var bool
     */
    protected $isTransaction = false;
    /**
     * 最后插入行的ID或序列值
     * @var null
     */
    public $lastInsertId = null;
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [];

    /**
     * Indicates if the IDs are auto-incrementing.
     *
     * @var bool
     */
    public $incrementing = true;
    //private标记的构造方法
    private function __construct()
    {

    }
    //创建__clone方法防止对象被复制克隆
    public function __clone()
    {
        trigger_error('Clone is not allow!',E_USER_ERROR);

    }
    //单例方法,用于访问实例的公共的静态方法
    public static function getInstance()
    {
        if(!(self::$_instance instanceof self)){
            self::$_instance = new self;
        }

        return self::$_instance;
    }

    /**
     * 初始化连接
     * @param array $config
     * @return $this
     * @throws Exception
     */
    public function connect($config = array())
    {
        if(self::$dbh === null){
            if(!empty($config)){
                $this->setConfig($config);
            }else{ //如果没有传递配置参数这使用配置文件默认参数
                //根据框架里面获取配置文件的参数来写
                $config = array();
                $this->setConfig($config);
            }
        }
        return $this;
    }

    /**
     * $connect = [
    'driver'=>'mysql',
    'db'=>'test',
    'host'=>'127.0.0.1',
    'username'=>'root',
    'password'=>'',
    'driver_options'=>''
    ];
     * @param $config
     * @return $this
     */
    protected function setConfig($config)
    {
        if(!is_array($config)){
            $this->throwException('配置文件格式不对');
        }
        $dsn = "{$config['driver']}:dbname={$config['db']};host={$config['host']}";
        $username = isset($config['username']) ? $config['username'] : null;
        $password = isset($config['password']) ? $config['password'] : null;
        $driver_options = isset($config['driver_options']) ? $config['driver_options'] : array();
        try{
            self::$dbh = new PDO($dsn,$username,$password,$driver_options); //连接pdo
            return $this;
        }catch (PDOException $e){
            die($e->getMessage());
        }
    }

    /**
     * where 条件查询
     * @param string $fileds
     * @return mixed
     */
    public function select($fileds = '*')
    {
        $this->_sql = '';
        if(is_array($fileds)){
            foreach($fileds as $filed){
                $this->_sql .="SELECT `{$filed}`,";
            }
        }elseif(is_string($fileds)){
            $this->_sql .="SELECT {$fileds} ";
        }else{
            $this->throwException('字段类型只支持字符串或者数组');
        }

        $where = trim($this->_where,'and or');
        if(strlen($where)){
            $where = "WHERE {$where} ";
        }
        $order = trim($this->_order,',');
        if(strlen($order)){
            $order = "ORDER BY {$order}";
        }

        $this->_sql = trim(trim($this->_sql,',')." FROM {$this->table_name} {$where} {$order} ".$this->_limit);

        $stmt = self::$dbh->prepare($this->_sql);

        foreach($this->_bind as $key=>$bind){
            $stmt->bindParam($key+1,$this->_bind[$key],$this->checkType($bind));
        }
        $stmt->execute();
        $fetch_style = $this->fetchType('assoc');

        $rows = $stmt->fetchAll($fetch_style);
        return $rows;
    }

    /**
     * 检查绑定参数数据类型
     * @param $bind
     * @return int
     */
    protected function checkType($bind)
    {
        $data_type = PDO::PARAM_STR;
        if(is_string($bind) || is_object($bind)){ //字符串
            $data_type = PDO::PARAM_STR;
        }
        if(is_resource($bind)){ //资源
            $data_type = PDO::PARAM_LOB;
        }
        if(is_numeric($bind)){
            $data_type = PDO::PARAM_INT;
        }
        return $data_type;
    }
    /**
     * 设置数据表名
     * @param null $table_name
     * @return $this
     */
    public function table($table_name=null)
    {
        $this->table_name = $table_name;
        return $this;
    }

    /**
     * 递归循环拼装where条件
     * @param $where
     */
    protected function recursiveWhere($where)
    {
        if(is_array($where)){
            foreach($where as $k=>$w){
                if(!array_key_exists('_logic',$where)){
                    $where['_logic'] = 'and';
                }
                if(!in_array(strtolower($k),['_logic','_complex'])){
                    $this->_where .= $this->checkWhereType($k,$w);
                    $this->_where = $this->_where . $where['_logic']." ";
                    if(is_array($w[1])){
                        foreach($w[1] as $w_v){
                            $this->_bind[] = $w_v;
                        }
                    }else{
                        $this->_bind[] = $w[1];
                    }
                }else{
                    if(strtolower($k) == '_complex'){
                        $this->recursiveWhere($w);
                        $this->_where = "(".$this->_where.") {$where['_logic']} ";
                    }
                }
            }
            $this->_where = trim($this->_where,'or and');
        }
    }

    protected function checkWhereType($field,$where)
    {
        $w = '';
        $key = $field;
        switch(strtoupper($where[0])){
            case 'LT':
                $where[0] = "<=";
            break;
            case 'GT':
                $where[0] = ">=";
            break;
            case 'EQ':
                $where[0] = "=";
            break;
            case 'NEQ':
                $where[0] = "!=";
             break;

        }
        switch(strtoupper($where[0])){
            case '=':
            case '!=':
            case '>':
            case '>=':
            case '<=':
                $w .="`{$key}` {$where[0]} ? ";
                break;
            case 'BETWEEN':
            case 'NOT BETWEEN':
                if(is_array($where[1])){
                    $w .= "`{$key}` {$where[0]} ? AND ? ";
                }else{
                    $this->throwException('where 传参类型不正确');
                }
                break;
            case 'IN':
            case 'NOT IN':
                if(is_array($where[1])){
                    $w .= "`{$key}` {$where[0]} (?,?) ";
                }else{
                    $this->throwException('where 传参类型不正确');
                }
                break;
            case 'LIKE':
            case 'NOT LIKE':
                //%号的处理
                $w .="`{$key}` {$where[0]} ? ";
                break;
            default:
                $w .="`{$key}` = ? ";
            //'BETWEEN','NOT BETWEEN','IN','NOT IN','LIKE','NOT LIKE','IS NULL','NOT LIKE','IS NOT NULL'
        }
        return $w;
    }


    public function where($where = array())
    {
        $this->recursiveWhere($where);
        return $this;
    }
    /**
     *
     * @param array $where
     * @return $this
     * @throws Exception
     */

    public function orWhere($where = array())
    {
        $num = func_num_args();
        switch($num){
            case 1:
                switch(count($where)){
                    case 2:
                        $this->_where .= "OR `{$where[0]}` = ? ";
                        $this->_bind[] = $where[1];
                        break;
                    case 3:
                        $this->_where .= "OR ".$this->whereType($where);
                        foreach($where[2] as $bind_param){
                            $this->_bind[] = $bind_param;
                        }
                        break;
                    default:
                        $this->throwException('where 传参类型不正确');
                }
                break;
        }
        return $this;
    }

    /**
     * 各种where条件判断
     * @param $where
     * @return string
     * @throws Exception
     */
    private function whereType($where)
    {
        $w = '';
        switch($where[1]){
            case '=':
            case '!=':
            case '>':
            case '>=':
            case '<=':
                $w ="`{$where[0]}` {$where[1]} ? ";
            break;
            case 'BETWEEN':
            case 'NOT BETWEEN':
                if(is_array($where[2])){
                    $w = "`{$where[0]}` {$where[1]} ? AND ? ";
                }else{
                    $this->throwException('where 传参类型不正确');
                }
            break;
            case 'IN':
            case 'NOT IN':
                if(is_array($where[2])){
                    $w = "`{$where[0]}` {$where[1]} (?,?) ";
                }else{
                    $this->throwException('where 传参类型不正确');
                }
            break;
            case 'LIKE':
            case 'NOT LIKE':
            //%号的处理
            $w ="`{$where[0]}` {$where[1]} ? ";
            break;
            default:
                $w ="`{$where[0]}` = ? ";
                //'BETWEEN','NOT BETWEEN','IN','NOT IN','LIKE','NOT LIKE','IS NULL','NOT LIKE','IS NOT NULL'
        }
        return $w;
    }

    /**
     * 排序条件
     * @param $order
     * @return $this
     */
    public function order($order)
    {
        if(is_string($order)){
            $this->_order .= "{$order},";
        }
        if(is_array($order)){
            foreach($order as $key=>$value){
                $this->_order .=$key." ".$value.",";
            }

        }
        return $this;
    }

    /**
     * 分页查询条件
     * @param int $offset
     * @param null $skip
     * @return $this
     */
    public function limit($offset=0,$skip=null)
    {
        $skip = $skip ? $skip : $this->perPage;
        $this->_limit = "limit {$offset},{$skip}";
        return $this;
    }
    /**
     * 直接执行原生sql 没有预处理
     * @param $sql
     * @param string $type
     * @return mixed
     */
    public function query($sql,$type='assoc')
    {
        $this->_sql = $sql;
        $stmt = self::$dbh->query($sql);
        $fetch_style = $this->fetchType($type);
        $rows = $stmt->fetchAll($fetch_style);
        return $rows;
    }

    /**
     * 返回不同格式的数据 对象 关联数组 索引数组
     * @param $type
     * @return int
     */
    public function fetchType($type)
    {
        $fetch_style = PDO::FETCH_ASSOC;
        switch(strtolower($type)){
            case "both":
                $fetch_style = PDO::FETCH_BOTH;
                break;
            case 'num':
                $fetch_style = PDO::FETCH_NUM;
                break;
            case 'obj':
                $fetch_style = PDO::FETCH_OBJ;
                break;
            default:
                $fetch_style = PDO::FETCH_ASSOC;
        }
        return $fetch_style;
    }


    /**
     * 单条数据插入
     * @param array $data
     * @return null 获取最后一条插入的id值
     */
    public function create($data = array())
    {
        if(is_array($data) && empty($data)){
            $post_data = $_POST;
        }else{
            $post_data = $data;
        }
        $new_post_datas = $this->filterField($post_data);
        // INSERT INTO tbl_name (col1,col2) VALUES(15,col1*2);
        $columns = array_keys($new_post_datas);
        $this->_bind = array_values($new_post_datas);
        if(count($columns)){
            $columns_str = implode(',',$columns);
            $bind_str = trim(str_repeat('?,',count($columns)),',');
            $this->_sql = "insert into {$this->table_name}({$columns_str}) VALUES({$bind_str})";

            $stmt = self::$dbh->prepare($this->_sql);
            foreach($this->_bind as $key=>$bind){
                $stmt->bindParam($key+1,$this->_bind[$key],$this->checkType($bind));
            }
            $stmt->execute();
            $this->lastInsertId = self::$dbh->lastInsertId();
            return $this->lastInsertId;
        }
    }

    /**
     * 批量插入数据 返回批量插入的数量
     * @param array $datas
     * @return mixed
     */
    public function batchInsert($datas = array())
    {
        if(is_array($datas)){
            $columns_str = '';
            $bind_str = '';
            foreach ($datas as $data){
                $data = $this->filterField($data);
                $columns = array_keys($data);
                $this->_bind = array_merge($this->_bind,array_values($data));
                if(count($columns)){
                    $columns_str = "(".implode(',',$columns).")";
                    $bind_str .= "(".trim(str_repeat('?,',count($columns)),',')."),";

                }
            }
            $columns_str = trim($columns_str,',');
            $bind_str = trim($bind_str,',');
            $this->_sql = "insert into {$this->table_name}{$columns_str} VALUES{$bind_str}";

            $stmt = self::$dbh->prepare($this->_sql);
            foreach($this->_bind as $key=>$bind){
                $stmt->bindParam($key+1,$this->_bind[$key],$this->checkType($bind));
            }
            $stmt->execute();
            $rowCount = $stmt->rowCount();
            return $rowCount;
        }
    }
    /**
     * 修改数据
     * @param $data
     * @return mixed 返回影响行数
     */
    public function update($data)
    {
        //UPDATE items SET items.price=? WHERE items.id=month.id;
        $new_datas = $this->filterField($data);
        $update_str = '';
        if(is_array($new_datas) && !empty($new_datas)){

            foreach ($new_datas as $key=>$new_data){
                $update_str .="{$this->table_name}.`{$key}`=?,";
            }
            $new_datas = array_reverse($new_datas,true);
            foreach ($new_datas as $key=>$new_data){
                $this->_bind[] = $new_data;
            }

            $this->_bind = array_reverse($this->_bind);
            $update_str = trim($update_str,',');
            $where = trim($this->_where,'and or');
            if(strlen($where)){
                $where = "WHERE {$where} ";
            }
            $this->_sql = "update {$this->table_name} set {$update_str} {$where}";
        }
        return $this->exec();
    }

    /**
     * 根据where条件删除 数据
     * @param bool $is_delete_all
     * @return mixed 返回删除的数据条数
     */
    public function delete($is_delete_all = false)
    {
        if($is_delete_all === true){
            $this->_sql = "delete from {$this->table_name}";
        }else{
            $where = $this->getWhere();
            $this->_sql = "delete from {$this->table_name} {$where}";
            if(empty($where)){
                $this->throwException('删除操作请传入删除条件');
            }
        }
        return $this->exec();
    }

    protected function exec()
    {
        $stmt = self::$dbh->prepare($this->_sql);
        foreach($this->_bind as $key=>$bind){
            $stmt->bindParam($key+1,$this->_bind[$key],$this->checkType($bind));
        }
        $stmt->execute();
        $rowCount = $stmt->rowCount();
        return $rowCount;
    }
    protected function getWhere()
    {
        $where = trim($this->_where,'and or');
        if(strlen($where)){
            $where = "WHERE {$where} ";
        }
        return $where;
    }
    /**
     * 过滤存入数据库中的字段
     * @param $input_fields_data
     * @return array
     */
    protected function filterField($input_fields_data)
    {
        $fillable = $this->fillable;
        $new_data = array();
        if(is_array($fillable) && !empty($fillable)){ //过滤数据字段
            foreach($input_fields_data as $key=>$input_fields){
                if(in_array($key,$fillable)){
                    $new_data[$key] = $input_fields;
                }
            }
            $input_fields_data = $new_data;
        }
        return $input_fields_data;
    }

    /**
     * 开启事务处理
     */
    public function beginTransaction()
    {
        if(!$this->isTransaction){
            self::$dbh->beginTransaction();
            $this->isTransaction = true;
        }
    }

    /**
     * 提交事务
     */
    public function commit()
    {
        if($this->isTransaction){
            self::$dbh->commit();
        }else{
            $this->throwException('请先开启事务');
        }
    }

    /**
     * 回滚事务
     */
    public function rollBack()
    {
        if($this->isTransaction){
            self::$dbh->rollBack();
        }else{
            $this->throwException('请先开启事务');
        }
    }
    /**
     * 获取最后一条sql语句
     * @return null
     */
    public function _lastsql()
    {
        return $this->_sql;
    }

    /**
     * 抛出异常
     * @param $msg
     */
    public function throwException($msg){
        try{
            throw new Exception($msg);
        } catch(Exception $e){
            die($e->getMessage());
        }
    }

    public function __call($name, $arguments)
    {
        $parameters_names = $this->get_class_func_params($name);
        switch (count($parameters_names)){
            case 1:
                return $this->$name($arguments[0]);
            break;
            case 2:
                return $this->$name($arguments[0],$arguments[1]);
            break;
            case 3:
                return $this->$name($arguments[0],$arguments[1],$arguments[2]);
                break;
            case 4:
                return $this->$name($arguments[0],$arguments[1],$arguments[2],$arguments[3]);
            break;
            case 5:
                return $this->$name($arguments[0],$arguments[1],$arguments[2],$arguments[3],$arguments[4]);
            break;
            case 6:
                return $this->$name($arguments[0],$arguments[1],$arguments[2],$arguments[3],$arguments[4],$arguments[5]);
            break;
            default:
                return $this->$name();
        }

    }

//    public function get_func_params($func)
//    {
//        $r = new ReflectionFunction($func);
//        $str = $r->getDocComment();
//        preg_match_all('/@param/',$str,$m);
//        return count($m[0]);
//    }

    /**
     * 获取类中函数参数个数
     * @param $func_name
     * @return array
     */
    public function get_class_func_params($func_name)
    {
        if(is_string($func_name) && !empty($func_name)){
            $class_name = get_class($this);
            $reflector = new ReflectionClass($class_name);

            $parameters = $reflector->getMethod($func_name)->getParameters();
            $parameters_names = array();
            foreach($parameters as $param)
            {
                $parameters_names[] = $param->getName();
            }
            return $parameters_names;
        }

    }

    /**
     * 析构方法
     */
    public function __destruct()
    {
       
    }

    public function changeDb($db_name,$username='root',$password='',$host='127.0.0.1',$driver = 'mysql',$driver_options = array())
    {
        $config = [
            'driver'=>$driver,
            'db'=>$db_name,
            'host'=>$host,
            'username'=>"{$username}",
            'password'=>"{$password}",
        ];
        if(is_array($driver_options) && !empty($driver_options)){
            $config['driver_options'] = $driver_options;
        }

        $this->setConfig($config);
        return $this;
    }
}


